Track/course upload for Garmin fitness. From Martin Buck. (His "Patch #4")
authorrobertl <robertl>
Sun, 7 Mar 2010 04:23:42 +0000 (04:23 +0000)
committerrobertl <robertl>
Sun, 7 Mar 2010 04:23:42 +0000 (04:23 +0000)
jeeps/gps.h
jeeps/gpsapp.c
jeeps/gpsapp.h
jeeps/gpscom.c
jeeps/gpscom.h
jeeps/gpsmem.c
jeeps/gpsmem.h

index 54c492f965846b2fe28d4d8b9ef235a8e3d20087..92d932a7b51dc396667fe78fe89873455fc1b713 100644 (file)
@@ -197,6 +197,57 @@ typedef struct GPS_SLap {
        */
 } GPS_OLap, *GPS_PLap;
 
+
+typedef struct GPS_SCourse
+{
+    uint32    index;                    /* Unique among courses on device */
+    char      course_name[16];          /* Null-terminated unique course name */
+    uint32    track_index;              /* Index of the associated track
+                                         * Must be 0xFFFFFFFF if there is none*/
+} GPS_OCourse, *GPS_PCourse;
+
+
+typedef struct GPS_SCourse_Lap
+{
+    uint32        course_index;         /* Index of associated course */
+    uint32        lap_index;            /* This lap's index in the course */
+    uint32        total_time;           /* In hundredths of a second */
+    float         total_dist;           /* [m] */
+    double        begin_lat;            /* Starting position of the lap */
+    double        begin_lon;            /* Invalid if lat,lon are 0x7FFFFFFF.*/
+    double        end_lat;              /* Final position of the lap */
+    double        end_lon;              /* Invalid if lat,lon are 0x7FFFFFFF.*/
+    UC            avg_heart_rate;       /* In beats-per-minute, >0 */
+    UC            max_heart_rate;       /* In beats-per-minute, >0 */
+    UC            intensity;            /* 0=standard, active lap.
+                                           1=rest lap in a workout */
+    UC            avg_cadence;          /* In revolutions-per-minute */
+} GPS_OCourse_Lap, *GPS_PCourse_Lap;
+
+typedef struct GPS_SCourse_Point
+{
+    char        name[11];               /* Null-terminated name */
+    uint32      course_index;           /* Index of associated course */
+    time_t      track_point_time;       /* Time */
+    UC          point_type;             /* generic = 0,
+                                         * summit = 1,
+                                         * valley = 2,
+                                         * water = 3,
+                                         * food = 4,
+                                         * danger = 5,
+                                         * left = 6,
+                                         * right = 7,
+                                         * straight = 8,
+                                         * first_aid = 9,
+                                         * fourth_category = 10,
+                                         * third_category = 11,
+                                         * second_category = 12,
+                                         * first_category = 13,
+                                         * hors_category = 14,
+                                         * sprint = 15 */
+} GPS_OCourse_Point, *GPS_PCourse_Point;
+
+
 typedef int (*pcb_fn) (int, struct GPS_SWay **);
 
 #include "gpsdevice.h"
index fac1d68919eeb9143c54b144844335c736fe2d6a..860c5c4e2ada12aa6188dc2bddb782a6592b4b15 100644 (file)
@@ -104,6 +104,8 @@ static void   GPS_D501_Send(UC *data, GPS_PAlmanac alm);
 static void   GPS_D550_Send(UC *data, GPS_PAlmanac alm);
 static void   GPS_D551_Send(UC *data, GPS_PAlmanac alm);
 
+static UC Is_Trackpoint_Invalid(GPS_PTrack *trk);
+
 
 int32  gps_save_id;
 int    gps_is_usb;
@@ -3776,7 +3778,163 @@ int32 GPS_A301_Get(const char *port, GPS_PTrack **trk, pcb_fn cb)
 }
 
 
+/* @func GPS_A302_Get ******************************************************
+**
+** Get course track data from GPS with protocol A302
+**
+** @param [r] port [const char *] serial port
+** @param [w] trk [GPS_PTrack **] track array
+**
+** @return [int32] number of track entries
+************************************************************************/
+/* FIXME: Merge with GPS_A301_Get()? */
+int32 GPS_A302_Get(const char *port, GPS_PTrack **trk, pcb_fn cb)
+{
+    static UC data[2];
+    gpsdevh *fd;
+    GPS_PPacket tra;
+    GPS_PPacket rec;
+    int32 n;
+    int32 i;
+
+    if(gps_trk_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    /* Only those GPS' with L001 can send track data */
+    if  (!LINK_ID[gps_link_type].Pid_Course_Trk_Data)
+    {
+       GPS_Warning("Course track data unsupported");
+       return GPS_UNSUPPORTED;
+    }
+
+    if(!GPS_Device_On(port, &fd))
+       return gps_errno;
+
+    if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,
+                    COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Tracks);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Command_Data,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+       return gps_errno;
+    if(!GPS_Packet_Read(fd, &rec))
+       return gps_errno;
+    if(!GPS_Send_Ack(fd, &tra, &rec))
+       return gps_errno;
+
+
+    n = GPS_Util_Get_Short(rec->data);
+
+    if(n)
+       if(!((*trk)=(GPS_PTrack *)malloc(n*sizeof(GPS_PTrack))))
+       {
+           GPS_Error("A302b_Get: Insufficient memory");
+           return MEMORY_ERROR;
+       }
+    for(i=0;i<n;++i)
+       if(!((*trk)[i]=GPS_Track_New()))
+           return MEMORY_ERROR;
+
+    for(i=0;i<n;++i)
+    {
+       if(!GPS_Packet_Read(fd, &rec))
+           return gps_errno;
+       if(!GPS_Send_Ack(fd, &tra, &rec))
+           return gps_errno;
+       if (rec->type == LINK_ID[gps_link_type].Pid_Course_Trk_Hdr)
+       {
+           switch(gps_run_crs_trk_hdr_type)
+           {
+           case pD310:
+           case pD312:
+               GPS_D310_Get(&((*trk)[i]),rec->data);
+               break;
+           case pD311:
+               GPS_D311_Get(&((*trk)[i]),rec->data);
+               break;
+           default:
+               GPS_Error("A302b_Get: Unknown track header data");
+               return PROTOCOL_ERROR;
+           }
+           (*trk)[i]->ishdr = 1;
+           continue;
+       }
+
+       if  (rec->type != LINK_ID[gps_link_type].Pid_Course_Trk_Data)
+       {
+           GPS_Error("A302b_Get: Non-Pid_Course_Trk_Data");
+           return FRAMING_ERROR;
+       }
+
+       (*trk)[i]->ishdr = 0;
+
+       switch(gps_run_crs_trk_type)
+       {
+       case pD300:
+           GPS_D300b_Get(&((*trk)[i]),rec->data);
+           break;
+       case pD301:
+           GPS_D301b_Get(&((*trk)[i]),rec->data);
+           break;
+       case pD302:
+           GPS_D302b_Get(&((*trk)[i]),rec->data);
+           break;
+       case pD303:
+       case pD304:
+           GPS_D303b_Get(&((*trk)[i]),rec->data);
+           /* Fitness devices don't send track segment markers, so we have
+            * to create them ourselves. We do so at the beginning of the
+            * track or if the device signals a pause by sending two
+            * invalid points in a row.
+            */
+            if (i>0)
+            {
+                if ((*trk)[i-1]->ishdr ||
+                    (Is_Trackpoint_Invalid(&((*trk)[i-1])) &&
+                     Is_Trackpoint_Invalid(&((*trk)[i]))))
+                {
+                    (*trk)[i]->tnew = 1;
+                }
+            }
+           break;
+       default:
+           GPS_Error("A301_GET: Unknown track protocol");
+           return PROTOCOL_ERROR;
+       }
+       /* Cheat and don't _really_ pass the trkpt back */
+       if (cb) {
+           cb(n, NULL);
+        }
+    }
+
+    if(!GPS_Packet_Read(fd, &rec))
+       return gps_errno;
+    if(!GPS_Send_Ack(fd, &tra, &rec))
+       return gps_errno;
+    if(rec->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt)
+    {
+       GPS_Error("A302b_Get: Error transferring course tracks");
+       return FRAMING_ERROR;
+    }
+
+    if(i != n)
+    {
+       GPS_Error("A302b_Get: Course track entry number mismatch");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&tra);
+    GPS_Packet_Del(&rec);
+
+    if(!GPS_Device_Off(fd))
+       return gps_errno;
 
+    return n;
+}
 
 
 /* @func GPS_A300_Send **************************************************
@@ -3830,8 +3988,7 @@ int32 GPS_A300_Send(const char *port, GPS_PTrack *trk, int32 n)
        switch(gps_trk_type)
        {
        case pD300:
-           GPS_D300_Send(data,trk[i]);
-           len = 13;
+           GPS_D300_Send(data,trk[i],&len);
            break;
        default:
            GPS_Error("A300_Send: Unknown track protocol");
@@ -3896,11 +4053,6 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n)
     if(gps_trk_transfer == -1)
        return GPS_UNSUPPORTED;
 
-    if(gps_trk_transfer == 302) {
-       GPS_Warning("A302: Device does not support sending tracks to it. ");
-       return GPS_UNSUPPORTED;
-    }
-
     /* Only those GPS' with L001 can send track data */
     if(!LINK_ID[gps_link_type].Pid_Trk_Data)
     {
@@ -3953,20 +4105,19 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n)
            switch(gps_trk_type)
            {
            case pD300:
-               GPS_D300_Send(data,trk[i]);
-               len = 13;
+               GPS_D300_Send(data,trk[i],&len);
                break;
            case pD301:
-               GPS_D301_Send(data,trk[i], 301);
-               len = 21;
+               GPS_D301_Send(data,trk[i],&len,301);
                break;
            case pD302:
-               GPS_D301_Send(data,trk[i], 302);
-               len = 25;
+               GPS_D301_Send(data,trk[i],&len,302);
+               break;
+           case pD303:
+               GPS_D303_Send(data,trk[i],&len,303);
                break;
            case pD304:
-               GPS_D304_Send(data,trk[i]);
-               len = 23;
+               GPS_D303_Send(data,trk[i],&len,304);
                break;
            default:
                GPS_Error("A301_Send: Unknown track protocol");
@@ -4009,6 +4160,135 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n)
 }
 
 
+/* @func GPS_A302_Send **************************************************
+**
+** Send Course-track log to GPS
+**
+** Note that different to other GPS_Axxx_Send functions, the device
+** communication is not initialized/ended within the function, since
+** this packet transfer is only part of a series of transfers to the
+** device. Communication init/end has to be handled by the caller.
+**
+** @param [r] port [const char *] serial port
+** @param [r] trk [GPS_PTrack *] track array
+** @param [r] n [int32] number of track entries
+** @param [r] fd [gpsdevh *] pointer to the communication port
+**
+** @return [int32] success
+************************************************************************/
+/* FIXME: Merge with GPS_A301_Send()? */
+int32 GPS_A302_Send(const char *port,
+                     GPS_PTrack *trk,
+                     int32 n,
+                     gpsdevh *fd)
+{
+    UC data[GPS_ARB_LEN];
+    GPS_PPacket tra;
+    GPS_PPacket rec;
+    int32 i;
+    int32 len;
+    US  method;
+
+    if(gps_trk_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    /* Only those GPS' with L001 can send track data */
+    if(!LINK_ID[gps_link_type].Pid_Course_Trk_Data)
+    {
+       GPS_Warning("A302b: course-track protocol unsupported");
+       return GPS_UNSUPPORTED;
+    }
+
+    if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,(US) n);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A302_Send: Course-track start data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    for(i=0;i<n;++i)
+    {
+       if(trk[i]->ishdr)
+       {
+           method = LINK_ID[gps_link_type].Pid_Course_Trk_Hdr;
+
+           switch(gps_run_crs_trk_hdr_type)
+           {
+           case pD310:
+           case pD312:
+               GPS_D310_Send(data,trk[i],&len);
+               break;
+           case pD311:
+               GPS_D311_Send(data,trk[i],&len);
+               break;
+           default:
+               GPS_Error("A302_Send: Unknown track header type");
+               return PROTOCOL_ERROR;
+           }
+       }
+       else
+       {
+           method = LINK_ID[gps_link_type].Pid_Course_Trk_Data;
+
+           switch(gps_run_crs_trk_type)
+           {
+           case pD300:
+               GPS_D300_Send(data,trk[i],&len);
+               break;
+           case pD301:
+               GPS_D301_Send(data,trk[i],&len, 301);
+               break;
+           case pD302:
+               GPS_D301_Send(data,trk[i],&len, 302);
+               break;
+           case pD303:
+               GPS_D303_Send(data,trk[i],&len,303);
+               break;
+           case pD304:
+               GPS_D303_Send(data,trk[i],&len,304);
+               break;
+           default:
+               GPS_Error("A302_Send: Unknown track protocol");
+               return PROTOCOL_ERROR;
+           }
+       }
+
+       GPS_Make_Packet(&tra, method, data,(US) len);
+
+       if(!GPS_Write_Packet(fd,tra))
+           return gps_errno;
+
+       if(!GPS_Get_Ack(fd, &tra, &rec))
+       {
+          GPS_Error("A302_Send: Course-track packet not acknowledged");
+          return FRAMING_ERROR;
+       }
+    }
+
+    GPS_Util_Put_Short(data,COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Tracks);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Xfer_Cmplt,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A302_Send: Course-track complete data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&tra);
+    GPS_Packet_Del(&rec);
+
+    return 1;
+}
+
 
 /* @func GPS_D300_Get ******************************************************
 **
@@ -4108,9 +4388,11 @@ void GPS_D301b_Get(GPS_PTrack *trk, UC *data)
        (*trk)->Time = GPS_Math_Gtime_To_Utime((time_t)t);
     p+=sizeof(uint32);
 
+    /* FIXME: check validity */
     (*trk)->alt = GPS_Util_Get_Float(p);
     p+=sizeof(float);
 
+    /* FIXME: check validity */
     (*trk)->dpth = GPS_Util_Get_Float(p);
     p+=sizeof(float);
 
@@ -4149,9 +4431,11 @@ void GPS_D302b_Get(GPS_PTrack *trk, UC *data)
        (*trk)->Time = GPS_Math_Gtime_To_Utime((time_t)t);
     p+=sizeof(uint32);
 
+    /* FIXME: check validity */
     (*trk)->alt = GPS_Util_Get_Float(p);
     p+=sizeof(float);
 
+    /* FIXME: check validity */
     (*trk)->dpth = GPS_Util_Get_Float(p);
     p+=sizeof(float);
 
@@ -4231,13 +4515,14 @@ void GPS_D303b_Get(GPS_PTrack *trk, UC *data)
 
     /* When latitude and longitude are undefined, this field seems to be
      * a constant on my receiver (51 59 04 69) */
+    /* FIXME: check validity */
     (*trk)->alt = GPS_Util_Get_Float(p);
     if (lat_undefined || lon_undefined) (*trk)->alt = 0.0f;
     p+=sizeof(float);
 
     /* Heartrate is reported as 0 if there is no signal from
      * a heartrate monitor.
-     *  305 and 304 are identical until now.
+     *  303 and 304 are identical until now.
      */
     switch (gps_trk_type) {
     case pD304:
@@ -4259,12 +4544,6 @@ void GPS_D303b_Get(GPS_PTrack *trk, UC *data)
        break;
     }
        
-    /* There doesn't seem to be a trk_seg bool, or at least I've not
-     * observed it yet.  One possibility is to start a new segment
-     * each time latitude and longitude are undefined? (Ie data from
-     * the heartrate monitor but none from the GPS. */
-    (*trk)->tnew = 0;  
-
     return;
 }
 
@@ -4326,16 +4605,19 @@ void GPS_D311_Get(GPS_PTrack *trk, UC *s)
 **
 ** @param [w] data [UC *] string to write to
 ** @param [r] trk [GPS_PTrack] track data
+** @param [w] len [int32 *] packet length
 **
 ** @return [void]
 ************************************************************************/
-void GPS_D300_Send(UC *data, GPS_PTrack trk)
+void GPS_D300_Send(UC *data, GPS_PTrack trk, int32 *len)
 {
     UC *p;
 
     p = data;
     GPS_A300_Encode(p,trk);
 
+    *len = 13;
+
     return;
 }
 
@@ -4343,14 +4625,16 @@ void GPS_D300_Send(UC *data, GPS_PTrack trk)
 
 /* @func GPS_D301_Send **************************************************
 **
-** Form track data string
+** Form track data string (D301 or D302, depending on type value)
 **
 ** @param [w] data [UC *] string to write to
 ** @param [r] trk [GPS_PTrack] track data
+** @param [w] len [int32 *] packet length
+** @param [r] type [int] track point type (301 or 302)
 **
 ** @return [void]
 ************************************************************************/
-void GPS_D301_Send(UC *data, GPS_PTrack trk, int type)
+void GPS_D301_Send(UC *data, GPS_PTrack trk, int32 *len, int type)
 {
     UC *p;
 
@@ -4364,16 +4648,32 @@ void GPS_D301_Send(UC *data, GPS_PTrack trk, int type)
     p+=sizeof(float);
 
     if (type == 302) {
+      /* Temperature */
       GPS_Util_Put_Float(p, 1.0e25f);
       p+=sizeof(float);
     }
 
     *p = trk->tnew;
+    p+=sizeof(UC);
+
+    *len = p-data;
 
     return;
 }
 
-void GPS_D304_Send(UC *data, GPS_PTrack trk)
+
+/* @func GPS_D303_Send **************************************************
+**
+** Form track data string (D303/D304)
+**
+** @param [w] data [UC *] string to write to
+** @param [r] trk [GPS_PTrack] track data
+** @param [w] len [int32 *] packet length
+** @param [r] protoid [int] track point type (303 or 304)
+**
+** @return [void]
+************************************************************************/
+void GPS_D303_Send(UC *data, GPS_PTrack trk, int32 *len, int protoid)
 {
     UC *p;
 
@@ -4384,19 +4684,23 @@ void GPS_D304_Send(UC *data, GPS_PTrack trk)
     GPS_Util_Put_Float(p,trk->alt);
     p+=sizeof(float);
 
-    GPS_Util_Put_Float(p, 1.0e25f);
-    p+=sizeof(float);
-
-    /* Not really clear if the members below makes sense to write or not */
+    if (protoid == 304) {
+       GPS_Util_Put_Float(p, 1.0e25f);
+       p+=sizeof(float);
+    }
 
     *p = trk->heartrate;
     p+=sizeof(char);
 
-    *p = trk->cadence;
-    p+=sizeof(char);
+    if (protoid == 304) {
+       *p = trk->cadence;
+       p+=sizeof(char);
 
-    *p = trk->cadence > 0 ? trk->cadence : 0xff;
-    p+=sizeof(char);
+       *p = trk->cadence > 0 ? trk->cadence : 0xff;
+       p+=sizeof(char);
+    }
+
+    *len = p-data;
 
     return;
 }
@@ -4414,12 +4718,9 @@ void GPS_D304_Send(UC *data, GPS_PTrack trk)
 void GPS_D311_Send(UC *data, GPS_PTrack trk, int32 *len)
 {
     UC *p;
-    int identifier;
 
     p = data;
-//    sscanf(trk->trk_ident, "%d", &identifier);
-    identifier = 0x1234;
-    GPS_Util_Put_Short(p,identifier);
+    GPS_Util_Put_Short(p,strtoul(trk->trk_ident, NULL, 0));
     p += 2;
     *len = p-data;
 
@@ -6304,117 +6605,894 @@ void GPS_D1011b_Get(GPS_PLap *Lap, UC *p)
 }
 
 
-/*
- *  It's unfortunate that these aren't constant and therefore switchable,
- *  but they really are runtime variable.  Sigh.
- */
-const char *
-Get_Pkt_Type(US p, US d0, const char **xinfo)
+/* @func GPS_A1006_Get **********************************************
+**
+** Get Courses from GPS. According to Garmin protocol specification, this
+** includes getting all course laps, course tracks and course points
+** from the device.
+**
+** @param [r] port [const char *] serial port
+** @param [w] crs [GPS_PCourse **] pointer to course array
+** @param [w] lap [GPS_PCourse_Lap **] pointer to course lap array
+** @param [w] trk [GPS_PTrack **] pointer to track array
+** @param [w] lap [GPS_PCourse_Point **] pointer to course point array
+** @param [w] n_lap [int32 **] pointer to number of lap entries
+** @param [w] n_trk [int32 **] pointer to number of track entries
+** @param [w] n_cpt [int32 **] pointer to number of course point entries
+**
+** @return [int32] number of course entries
+************************************************************************/
+
+int32  GPS_A1006_Get
+                (const char *port,
+                 GPS_PCourse **crs,
+                 pcb_fn cb)
+
 {
-       *xinfo = NULL;
-#define LT LINK_ID[gps_link_type]
-       if (p == LT.Pid_Ack_Byte)
-               return "ACK";
-       if (p == LT.Pid_Command_Data) {
-               switch (d0) {
-                       case 0: *xinfo = "Abort"; break;
-                       case 1: *xinfo = "Xfer Alm"; break;
-                       case 2: *xinfo = "Xfer Posn"; break;
-                       case 3: *xinfo = "Xfer Prx"; break;
-                       case 4: *xinfo = "Xfer Rte"; break;
-                       case 5: *xinfo = "Xfer Time"; break;
-                       case 6: *xinfo = "Xfer Trk"; break;
-                       case 7: *xinfo = "Xfer Wpt"; break;
-                       case 8: *xinfo = "Power Down"; break;
-                       case 49: *xinfo = "Xfer PVT Start"; break;
-                       case 50: *xinfo = "Xfer PVT Stop"; break;
-                       case 92: *xinfo = "Flight Records"; break;
-                       case 117: *xinfo = "Xfer Laps"; break;
-                       case 121: *xinfo = "Xfer Categories"; break;
-                       case 450: *xinfo = "Xfer Runs"; break;
-                       case 451: *xinfo = "Xfer Workouts"; break;
-                       case 452: *xinfo = "Xfer Wkt Occurrences"; break;
-                       case 453: *xinfo = "Xfer User Profile "; break;
-                       case 454: *xinfo = "Xfer Wkt Limits"; break;
-                       case 561: *xinfo = "Xfer Courses"; break;
-                       case 562: *xinfo = "Xfer Course Laps"; break;
-                       case 563: *xinfo = "Xfer Course Point"; break;
-                       case 564: *xinfo = "Xfer Course Tracks"; break;
-                       case 565: *xinfo = "Xfer Course Limits"; break;
+    static UC data[2];
+    gpsdevh *fd;
+    GPS_PPacket trapkt;
+    GPS_PPacket recpkt;
+    int32 i, n;
 
-                       default: *xinfo = "Unknown";
-               }
-               return "CMDDAT";
-       }
-       if (p == LT.Pid_Protocol_Array)
-               return "PRTARR";
-       if (p == LT.Pid_Product_Rqst)
-               return "PRDREQ";
-       if (p == LT.Pid_Product_Data)
-               return "PRDDAT";
-       if (p == LT.Pid_Ext_Product_Data)
-               return "PRDEDA";
+    if (gps_course_transfer == -1)
+       return GPS_UNSUPPORTED;
 
-       if (p == LT.Pid_Xfer_Cmplt)
-               return "XFRCMP";
-       if (p == LT.Pid_Date_Time_Data)
-               return "DATTIM";
-       if (p == LT.Pid_Position_Data)
-               return "POS";
-       if (p == LT.Pid_Prx_Wpt_Data)
-               return "WPT";
-       if (p == LT.Pid_Nak_Byte)
-               return "NAK";
-       if (p == LT.Pid_Records)
-               return "RECORD";
-       if (p == LT.Pid_Rte_Hdr)
-               return "RTEHDR";
-       if (p == LT.Pid_Rte_Wpt_Data)
-               return "RTEWPT";
-       if (p == LT.Pid_Almanac_Data)
-               return "RALMAN";
-       if (p == LT.Pid_Trk_Data)
-               return "TRKDAT";
-       if (p == LT.Pid_Wpt_Data)
-               return "WPTDAT";
-       if (p == LT.Pid_Pvt_Data)
-               return "PVTDAT";
-       if (p == LT.Pid_Rte_Link_Data)
-               return "LNKDAT";
-       if (p == LT.Pid_Trk_Hdr)
-               return "TRKHDR";
+    if (!GPS_Device_On(port, &fd))
+       return gps_errno;
 
-       if (p == LT.Pid_FlightBook_Record)
-               return "FLIBOO";
-       if (p == LT.Pid_Lap)
-               return "LAPDAT";
-       if (p == LT.Pid_Wpt_Cat)
-               return "WPTCAT";
-       if (p == LT.Pid_Run)
-               return "RUNDAT";
-       if (p == LT.Pid_Workout)
-               return "WKTDAT";
-       if (p == LT.Pid_Workout_Occurrence)
-               return "WKTOCC";
-       if (p == LT.Pid_Fitness_User_Profile)
-               return "UPROFI";
-       if (p == LT.Pid_Workout_Limits)
-               return "WKTLIM";
-       if (p == LT.Pid_Course)
-               return "CRSDAT";
-       if (p == LT.Pid_Course_Lap)
-               return "CRSLAP";
-       if (p == LT.Pid_Course_Point)
-               return "CRSPOI";
-       if (p == LT.Pid_Course_Trk_Hdr)
-               return "CRSTHD";
-       if (p == LT.Pid_Course_Trk_Data)
-               return "CRSTDA";
-       if (p == LT.Pid_Course_Limits)
-               return "CRSLIM";
-       if (p == LT.Pid_Trk2_Hdr)
-               return "TRKHD2";
-               
+    if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,
+                       COMMAND_ID[gps_device_command].Cmnd_Transfer_Courses);
+    GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data,
+                    data,2);
+    if(!GPS_Write_Packet(fd,trapkt))
+        return gps_errno;
+    if(!GPS_Get_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+    if(!GPS_Packet_Read(fd, &recpkt))
+        return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+
+    n = GPS_Util_Get_Short(recpkt->data);
+
+
+    if(n)
+        if(!((*crs)=(GPS_PCourse *)malloc(n*sizeof(GPS_PCourse))))
+        {
+            GPS_Error("A1006_Get: Insufficient memory");
+            return MEMORY_ERROR;
+        }
+
+    for(i=0;i<n;++i) {
+        if(!((*crs)[i]=GPS_Course_New()))
+            return MEMORY_ERROR;
+       if(!GPS_Packet_Read(fd, &recpkt)) {
+               return gps_errno;
+       }
+
+       if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) {
+               return gps_errno;
+       }
+
+       switch(gps_course_type) {
+            case pD1006:
+                GPS_D1006_Get(&((*crs)[i]),recpkt->data);
+               break;
+           default:
+               GPS_Error("A1006_Get: Unknown Course protocol %d\n",
+                                gps_course_type);
+               return PROTOCOL_ERROR;
+       }
+
+       // Cheat and don't _really_ pass the crs back
+       if (cb) {
+           cb(n, NULL);
+        }
+    }
+
+    if(!GPS_Packet_Read(fd, &recpkt))
+       return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+       return gps_errno;
+    if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) {
+       GPS_Error("A1006_Get: Error transferring courses");
+       return FRAMING_ERROR;
+    }
+
+    if(i != n) {
+       GPS_Error("A1006_GET: Course entry number mismatch");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&trapkt);
+    GPS_Packet_Del(&recpkt);
+
+    if (!GPS_Device_Off(fd))
+       return gps_errno;
+    return n;
+}
+
+
+/* @func GPS_A1006_Send **************************************************
+** Send Courses to GPS.
+**
+** Note that different to other GPS_Axxx_Send functions, the device
+** communication is not initialized/ended within the function, since
+** this packet transfer is only part of a series of transfers to the
+** device. Communication init/end has to be handled by the caller.
+**
+** @param [r] port [const char *] serial port
+** @param [r] crs [GPS_PCourse *] pointer to Course array
+** @param [r] n_wkt [int32] number of Course entries
+** @param [r] fd [gpsdevh *] pointer to the communication port
+**
+** @return [int32] success
+************************************************************************/
+int32 GPS_A1006_Send(const char *port,
+                     GPS_PCourse *crs,
+                     int32 n_crs,
+                     gpsdevh *fd)
+{
+    UC data[GPS_ARB_LEN];
+    GPS_PPacket tra;
+    GPS_PPacket rec;
+    int32 i;
+    int32 len;
+
+    if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,(US) n_crs);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A1006_Send: Course start data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    for(i=0;i<n_crs;++i)
+    {
+       switch(gps_course_type) {
+            case pD1006:
+                GPS_D1006_Send(data,crs[i],&len);
+               break;
+           default:
+               GPS_Error("A1006_Send: Unknown course type %d\n",
+                                                gps_course_type);
+               return PROTOCOL_ERROR;
+       }
+
+        GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Course,
+                        data,(US) len);
+
+       if(!GPS_Write_Packet(fd,tra))
+           return gps_errno;
+
+       if(!GPS_Get_Ack(fd, &tra, &rec))
+       {
+           GPS_Error("A1006_Send: Pid_Course not acknowledged");
+           return gps_errno;
+       }
+    }
+
+    GPS_Util_Put_Short(data,COMMAND_ID[gps_device_command].Cmnd_Transfer_Courses);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Xfer_Cmplt,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A1006_Send: Course complete data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&tra);
+    GPS_Packet_Del(&rec);
+
+    return 1;
+}
+
+
+/* @func GPS_D1006_Get ******************************************************
+**
+** Convert packet D1006 to course structure
+**
+** @param [w] crs [GPS_PCourse *] Course
+** @param [r] p [UC *] packet data
+**
+** @return [void]
+************************************************************************/
+void GPS_D1006_Get(GPS_PCourse *crs, UC *p)
+{
+    int i;
+    (*crs)->index = GPS_Util_Get_Short(p);
+    p+=sizeof(uint16);
+    p+=sizeof(uint16); // unused
+    for(i=0;i<16;++i)
+      (*crs)->course_name[i] = *p++;
+    (*crs)->track_index = GPS_Util_Get_Short(p);
+    p+=sizeof(uint16);
+}
+
+
+/* @funcstatic GPS_D1006_Send *******************************************
+**
+** Form course data string
+**
+** @param [w] data [UC *] string to write to
+** @param [r] crs [GPS_PCourse] course data
+** @param [w] len [int32 *] packet length
+**
+** @return [void]
+************************************************************************/
+void GPS_D1006_Send(UC *data, GPS_PCourse crs, int32 *len)
+{
+    UC *p;
+    int j;
+    p = data;
+
+    GPS_Util_Put_Short(p, (US) crs->index);
+    p += 2;
+
+    GPS_Util_Put_Uint(p,0);
+    p+=sizeof(uint16);
+
+    for(j=0;j<16;++j) *p++ = crs->course_name[j];
+
+    GPS_Util_Put_Short(p, (US) crs->track_index);
+    p += 2;
+
+    *len = p-data;
+
+    return;
+}
+
+
+/* @func GPS_A1007_Get ******************************************************
+**
+** Get course lap data from GPS
+**
+** @param [r] port [const char *] serial port
+** @param [w] clp [GPS_PCourse_Lap **] course lap array
+**
+** @return [int32] number of lap entries
+************************************************************************/
+
+int32 GPS_A1007_Get(const char *port, GPS_PCourse_Lap **clp, pcb_fn cb)
+{
+    static UC data[2];
+    gpsdevh *fd;
+    GPS_PPacket trapkt;
+    GPS_PPacket recpkt;
+    int32 i, n;
+
+    if (gps_course_lap_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    if (!GPS_Device_On(port, &fd))
+       return gps_errno;
+
+    if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,
+                       COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Laps);
+    GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data,
+                    data,2);
+    if(!GPS_Write_Packet(fd,trapkt))
+        return gps_errno;
+    if(!GPS_Get_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+    if(!GPS_Packet_Read(fd, &recpkt))
+        return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+
+    n = GPS_Util_Get_Short(recpkt->data);
+
+
+    if(n)
+        if(!((*clp)=(GPS_PCourse_Lap *)malloc(n*sizeof(GPS_PCourse_Lap))))
+        {
+            GPS_Error("A1007_Get: Insufficient memory");
+            return MEMORY_ERROR;
+        }
+
+    for(i=0;i<n;++i) {
+        if(!((*clp)[i]=GPS_Course_Lap_New()))
+            return MEMORY_ERROR;
+       if(!GPS_Packet_Read(fd, &recpkt)) {
+               return gps_errno;
+       }
+
+       if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) {
+               return gps_errno;
+       }
+
+       switch(gps_course_lap_type) {
+            case pD1007:
+                GPS_D1007_Get(&((*clp)[i]),recpkt->data);
+               break;
+           default:
+               GPS_Error("A1007_Get: Unknown Course Lap protocol %d\n",
+                                gps_course_lap_type);
+               return PROTOCOL_ERROR;
+       }
+
+       /* Cheat and don't _really_ pass the trkpt back */
+       if (cb) {
+           cb(n, NULL);
+        }
+    }
+
+    if(!GPS_Packet_Read(fd, &recpkt))
+       return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+       return gps_errno;
+    if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) {
+       GPS_Error("A1007_Get: Error transferring course laps");
+       return FRAMING_ERROR;
+    }
+
+    if(i != n) {
+       GPS_Error("A1007_GET: Course Lap entry number mismatch");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&trapkt);
+    GPS_Packet_Del(&recpkt);
+
+    if (!GPS_Device_Off(fd))
+       return gps_errno;
+    return n;
+}
+
+
+/* @func GPS_A1007_Send **************************************************
+** Send Course Lap to GPS.
+**
+** Note that different to other GPS_Axxx_Send functions, the device
+** communication is not initialized/ended within the function, since
+** this packet transfer is only part of a series of transfers to the
+** device. Communication init/end has to be handled by the caller.
+**
+** @param [r] port [const char *] serial port
+** @param [r] clp [GPS_PCourse_Lap *] pointer to CourseLap array
+** @param [r] n_clp [int32] number of CourseLap entries
+** @param [r] fd [gpsdevh *] pointer to the communication port
+**
+** @return [int32] success
+************************************************************************/
+int32 GPS_A1007_Send(const char *port,
+                     GPS_PCourse_Lap *clp,
+                     int32 n_clp,
+                     gpsdevh *fd)
+{
+    UC data[GPS_ARB_LEN];
+    GPS_PPacket tra;
+    GPS_PPacket rec;
+    int32 i;
+    int32 len;
+
+    if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,(US) n_clp);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A1007_Send: CourseLap start data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    for(i=0;i<n_clp;++i)
+    {
+       switch(gps_course_lap_type) {
+       case pD1007:
+           GPS_D1007_Send(data,clp[i],&len);
+           break;
+       default:
+           GPS_Error("A1007_Send: Unknown couse_lap type %d\n",
+                     gps_course_lap_type);
+           return PROTOCOL_ERROR;
+       }
+
+       GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Course_Lap,
+                       data,(US) len);
+
+       if(!GPS_Write_Packet(fd,tra))
+           return gps_errno;
+
+       if(!GPS_Get_Ack(fd, &tra, &rec))
+       {
+           GPS_Error("A1007_Send: Pid_Course_Lap not acknowledged");
+           return gps_errno;
+       }
+    }
+
+    GPS_Util_Put_Short(data,COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Laps);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Xfer_Cmplt,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A1007_Send: CourseLap complete data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&tra);
+    GPS_Packet_Del(&rec);
+
+    return 1;
+}
+
+
+/* @func GPS_D1007_Get ******************************************************
+**
+** Convert packet D1007 to course lap structure
+**
+** @param [r] packet [GPS_PPacket]       packet
+** @param [w] clp    [GPS_PCourse_Lap *] course lap structure
+**
+** @return [void]
+************************************************************************/
+void GPS_D1007_Get(GPS_PCourse_Lap *clp, UC *p)
+{
+    (*clp)->course_index = GPS_Util_Get_Short(p);
+    p+=sizeof(uint16);
+
+    (*clp)->lap_index = GPS_Util_Get_Short(p);
+    p+=sizeof(uint16);
+
+    (*clp)->total_time = GPS_Util_Get_Int(p);
+    p+=sizeof(uint32);
+
+    (*clp)->total_dist = GPS_Util_Get_Float(p);
+    p+=sizeof(float);
+
+    (*clp)->begin_lat = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p));
+    p+=sizeof(int32);
+    (*clp)->begin_lon = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p));
+    p+=sizeof(int32);
+    (*clp)->end_lat = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p));
+    p+=sizeof(int32);
+    (*clp)->end_lon = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p));
+    p+=sizeof(int32);
+
+    (*clp)->avg_heart_rate = *p++;
+    (*clp)->max_heart_rate = *p++;
+    (*clp)->intensity = *p++;
+    (*clp)->avg_cadence = *p++;
+
+    return;
+}
+
+
+/* @funcstatic GPS_D1007_Send *******************************************
+**
+** Form course lap data string
+**
+** @param [w] data [UC *] string to write to
+** @param [r] clp [GPS_PCourse_Lap] course lap data
+** @param [w] len [int32 *] packet length
+**
+** @return [void]
+************************************************************************/
+void GPS_D1007_Send(UC *data, GPS_PCourse_Lap clp, int32 *len)
+{
+    UC *p;
+    p = data;
+
+    GPS_Util_Put_Short(p, (US) clp->course_index);
+    p += 2;
+
+    GPS_Util_Put_Short(p, (US) clp->lap_index);
+    p += 2;
+
+    GPS_Util_Put_Uint(p, clp->total_time);
+    p+=sizeof(int32);
+
+    GPS_Util_Put_Float(p,clp->total_dist);
+    p+= sizeof(float);
+
+    GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->begin_lat));
+    p+=sizeof(int32);
+    GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->begin_lon));
+    p+=sizeof(int32);
+
+    GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->end_lat));
+    p+=sizeof(int32);
+    GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->end_lon));
+    p+=sizeof(int32);
+
+    *p++ = clp->avg_heart_rate;
+
+    *p++ = clp->max_heart_rate;
+
+    *p++ = clp->intensity;
+
+    *p++ = clp->avg_cadence;
+
+    *len = p-data;
+
+    return;
+}
+
+
+/* @func GPS_A1008_Get ******************************************************
+**
+** Get course points from GPS
+**
+** @param [r] port [const char *] serial port
+** @param [w] cpt [GPS_PCourse_Point **] course point array
+**
+** @return [int32] number of course point entries
+************************************************************************/
+
+int32 GPS_A1008_Get(const char *port, GPS_PCourse_Point **cpt, pcb_fn cb)
+{
+    static UC data[2];
+    gpsdevh *fd;
+    GPS_PPacket trapkt;
+    GPS_PPacket recpkt;
+    int32 i, n;
+
+    if (gps_course_point_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    if (!GPS_Device_On(port, &fd))
+       return gps_errno;
+
+    if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,
+                    COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Points);
+    GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data,
+                    data,2);
+    if(!GPS_Write_Packet(fd,trapkt))
+        return gps_errno;
+    if(!GPS_Get_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+    if(!GPS_Packet_Read(fd, &recpkt))
+        return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+        return gps_errno;
+
+    n = GPS_Util_Get_Short(recpkt->data);
+
+
+    if(n)
+        if(!((*cpt)=(GPS_PCourse_Point *)malloc(n*sizeof(GPS_PCourse_Point))))
+        {
+            GPS_Error("A1008_Get: Insufficient memory");
+            return MEMORY_ERROR;
+        }
+
+    for(i=0;i<n;++i) {
+        if(!((*cpt)[i]=GPS_Course_Point_New()))
+            return MEMORY_ERROR;
+       if(!GPS_Packet_Read(fd, &recpkt)) {
+               return gps_errno;
+       }
+
+       if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) {
+               return gps_errno;
+       }
+
+       switch(gps_course_point_type) {
+            case pD1012:
+                GPS_D1012_Get(&((*cpt)[i]),recpkt->data);
+               break;
+           default:
+               GPS_Error("A1008_Get: Unknown Course Point protocol %d\n",
+                                gps_course_point_type);
+               return PROTOCOL_ERROR;
+       }
+
+       /* Cheat and don't _really_ pass the trkpt back */
+       if (cb) {
+           cb(n, NULL);
+        }
+    }
+
+    if(!GPS_Packet_Read(fd, &recpkt))
+       return gps_errno;
+    if(!GPS_Send_Ack(fd, &trapkt, &recpkt))
+       return gps_errno;
+    if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) {
+       GPS_Error("A1008_Get: Error transferring course points");
+       return FRAMING_ERROR;
+    }
+
+    if(i != n) {
+       GPS_Error("A1008_GET: Course Point entry number mismatch");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&trapkt);
+    GPS_Packet_Del(&recpkt);
+
+    if (!GPS_Device_Off(fd))
+       return gps_errno;
+    return n;
+}
+
+
+/* @func GPS_A1008_Send **************************************************
+** Send Course Points to GPS.
+**
+** Note that different to other GPS_Axxx_Send functions, the device
+** communication is not initialized/ended within the function, since
+** this packet transfer is only part of a series of transfers to the
+** device. Communication init/end has to be handled by the caller.
+**
+**
+** @param [r] port [const char *] serial port
+** @param [r] cpt [GPS_PCourse_Point *] pointer to CoursePoint array
+** @param [r] n_cpt [int32] number of CoursePoint entries
+** @param [r] fd [gpsdevh *] pointer to the communication port
+**
+** @return [int32] success
+************************************************************************/
+int32 GPS_A1008_Send(const char *port,
+                     GPS_PCourse_Point *cpt,
+                     int32 n_cpt,
+                     gpsdevh *fd)
+{
+    UC data[GPS_ARB_LEN];
+    GPS_PPacket tra;
+    GPS_PPacket rec;
+    int32 i;
+    int32 len;
+
+    if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New()))
+       return MEMORY_ERROR;
+
+    GPS_Util_Put_Short(data,(US) n_cpt);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("GPS_A1008_Send: Coursepoint start data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    for(i=0;i<n_cpt;++i)
+    {
+       switch(gps_course_point_type) {
+       case pD1012:
+           GPS_D1012_Send(data,cpt[i],&len);
+           break;
+       default:
+           GPS_Error("GPS_A1008_Send: Unknown couse_point type %d\n",
+                     gps_course_point_type);
+           return PROTOCOL_ERROR;
+       }
+
+       GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Course_Point,
+                       data,(US) len);
+
+       if(!GPS_Write_Packet(fd,tra))
+           return gps_errno;
+
+       if(!GPS_Get_Ack(fd, &tra, &rec))
+       {
+           GPS_Error("A1008_Send: Pid_Course_Point not acknowledged");
+           return gps_errno;
+       }
+    }
+
+    GPS_Util_Put_Short(data,COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Points);
+    GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Xfer_Cmplt,
+                   data,2);
+    if(!GPS_Write_Packet(fd,tra))
+       return gps_errno;
+    if(!GPS_Get_Ack(fd, &tra, &rec))
+    {
+       GPS_Error("A1008_Send: CoursePoint complete data not acknowledged");
+       return FRAMING_ERROR;
+    }
+
+    GPS_Packet_Del(&tra);
+    GPS_Packet_Del(&rec);
+
+    return 1;
+}
+
+
+/* @func GPS_D1012_Get ******************************************************
+**
+** Convert packet D1012 to course point structure
+**
+** @param [w] cpt [GPS_PCourse_Point *] Course Point
+** @param [r] p [UC *] packet data
+**
+** @return [void]
+************************************************************************/
+void GPS_D1012_Get(GPS_PCourse_Point *cpt, UC *p)
+{
+    int i;
+    uint32 t;
+
+    for(i=0;i<11;++i)
+      (*cpt)->name[i] = *p++;
+    p++; //unused
+    (*cpt)->course_index = GPS_Util_Get_Short(p);
+    p+=sizeof(uint16);
+    p+=sizeof(uint16); // unused
+
+    t = GPS_Util_Get_Uint(p);
+    (*cpt)->track_point_time = GPS_Math_Gtime_To_Utime((time_t)t);
+    p+=sizeof(uint32);
+
+    (*cpt)->point_type = *p++;
+
+}
+
+
+/* @funcstatic GPS_D1012_Send *******************************************
+**
+** Form course point data string
+**
+** @param [w] data [UC *] string to write to
+** @param [r] cpt [GPS_PCourse_Point] course point data
+** @param [w] len [int32 *] packet length
+**
+** @return [void]
+************************************************************************/
+void GPS_D1012_Send(UC *data, GPS_PCourse_Point cpt, int32 *len)
+{
+    UC *p;
+    int j;
+    p = data;
+
+    for(j=0;j<11;++j) *p++ = cpt->name[j];
+
+    GPS_Util_Put_Uint(p,0);
+    p++;
+
+    GPS_Util_Put_Short(p, (US) cpt->course_index);
+    p += 2;
+
+    GPS_Util_Put_Uint(p,0);
+    p+=sizeof(uint16);
+
+    GPS_Util_Put_Uint(p,GPS_Math_Utime_To_Gtime(cpt->track_point_time));
+    p+=sizeof(uint32);
+
+    *p++ = cpt->point_type;
+
+    *len = p-data;
+
+    return;
+}
+
+
+/*
+ *  It's unfortunate that these aren't constant and therefore switchable,
+ *  but they really are runtime variable.  Sigh.
+ */
+const char *
+Get_Pkt_Type(US p, US d0, const char **xinfo)
+{
+       *xinfo = NULL;
+#define LT LINK_ID[gps_link_type]
+       if (p == LT.Pid_Ack_Byte)
+               return "ACK";
+       if (p == LT.Pid_Command_Data) {
+               switch (d0) {
+                       case 0: *xinfo = "Abort"; break;
+                       case 1: *xinfo = "Xfer Alm"; break;
+                       case 2: *xinfo = "Xfer Posn"; break;
+                       case 3: *xinfo = "Xfer Prx"; break;
+                       case 4: *xinfo = "Xfer Rte"; break;
+                       case 5: *xinfo = "Xfer Time"; break;
+                       case 6: *xinfo = "Xfer Trk"; break;
+                       case 7: *xinfo = "Xfer Wpt"; break;
+                       case 8: *xinfo = "Power Down"; break;
+                       case 49: *xinfo = "Xfer PVT Start"; break;
+                       case 50: *xinfo = "Xfer PVT Stop"; break;
+                       case 92: *xinfo = "Flight Records"; break;
+                       case 117: *xinfo = "Xfer Laps"; break;
+                       case 121: *xinfo = "Xfer Categories"; break;
+                       case 450: *xinfo = "Xfer Runs"; break;
+                       case 451: *xinfo = "Xfer Workouts"; break;
+                       case 452: *xinfo = "Xfer Wkt Occurrences"; break;
+                       case 453: *xinfo = "Xfer User Profile "; break;
+                       case 454: *xinfo = "Xfer Wkt Limits"; break;
+                       case 561: *xinfo = "Xfer Courses"; break;
+                       case 562: *xinfo = "Xfer Course Laps"; break;
+                       case 563: *xinfo = "Xfer Course Point"; break;
+                       case 564: *xinfo = "Xfer Course Tracks"; break;
+                       case 565: *xinfo = "Xfer Course Limits"; break;
+
+                       default: *xinfo = "Unknown";
+               }
+               return "CMDDAT";
+       }
+       if (p == LT.Pid_Protocol_Array)
+               return "PRTARR";
+       if (p == LT.Pid_Product_Rqst)
+               return "PRDREQ";
+       if (p == LT.Pid_Product_Data)
+               return "PRDDAT";
+       if (p == LT.Pid_Ext_Product_Data)
+               return "PRDEDA";
+
+       if (p == LT.Pid_Xfer_Cmplt)
+               return "XFRCMP";
+       if (p == LT.Pid_Date_Time_Data)
+               return "DATTIM";
+       if (p == LT.Pid_Position_Data)
+               return "POS";
+       if (p == LT.Pid_Prx_Wpt_Data)
+               return "WPT";
+       if (p == LT.Pid_Nak_Byte)
+               return "NAK";
+       if (p == LT.Pid_Records)
+               return "RECORD";
+       if (p == LT.Pid_Rte_Hdr)
+               return "RTEHDR";
+       if (p == LT.Pid_Rte_Wpt_Data)
+               return "RTEWPT";
+       if (p == LT.Pid_Almanac_Data)
+               return "RALMAN";
+       if (p == LT.Pid_Trk_Data)
+               return "TRKDAT";
+       if (p == LT.Pid_Wpt_Data)
+               return "WPTDAT";
+       if (p == LT.Pid_Pvt_Data)
+               return "PVTDAT";
+       if (p == LT.Pid_Rte_Link_Data)
+               return "LNKDAT";
+       if (p == LT.Pid_Trk_Hdr)
+               return "TRKHDR";
+
+       if (p == LT.Pid_FlightBook_Record)
+               return "FLIBOO";
+       if (p == LT.Pid_Lap)
+               return "LAPDAT";
+       if (p == LT.Pid_Wpt_Cat)
+               return "WPTCAT";
+       if (p == LT.Pid_Run)
+               return "RUNDAT";
+       if (p == LT.Pid_Workout)
+               return "WKTDAT";
+       if (p == LT.Pid_Workout_Occurrence)
+               return "WKTOCC";
+       if (p == LT.Pid_Fitness_User_Profile)
+               return "UPROFI";
+       if (p == LT.Pid_Workout_Limits)
+               return "WKTLIM";
+       if (p == LT.Pid_Course)
+               return "CRSDAT";
+       if (p == LT.Pid_Course_Lap)
+               return "CRSLAP";
+       if (p == LT.Pid_Course_Point)
+               return "CRSPOI";
+       if (p == LT.Pid_Course_Trk_Hdr)
+               return "CRSTHD";
+       if (p == LT.Pid_Course_Trk_Data)
+               return "CRSTDA";
+       if (p == LT.Pid_Course_Limits)
+               return "CRSLIM";
+       if (p == LT.Pid_Trk2_Hdr)
+               return "TRKHD2";
+               
        if (p == GUSB_REQUEST_BULK)
                return "REQBLK";
        if (p == GUSB_SESSION_START)
@@ -6424,3 +7502,26 @@ Get_Pkt_Type(US p, US d0, const char **xinfo)
                
        return "UNKNOWN";
 }
+
+
+/* @funcstatic Is_Trackpoint_Invalid ***********************************
+**
+** Check if a trackpoint is invalid. Needed for D303/D304 to check for
+** pauses.
+**
+**
+** @param [r] trk [GPS_PTrack *] track
+** @param [r] n [int32] Index of trackpoint
+**
+** @return [UC] 1 if the trackpoint is invalid
+************************************************************************/
+static UC Is_Trackpoint_Invalid(GPS_PTrack *trk)
+{
+    /* FIXME: We should have more *_is_unknown fields instead of
+     * checking for special values here (e.g. cadence = 0 would be
+     * perfectly valid, but GPS_D303b_Get() chose to use it to mark
+     * nonexistent cadence data.
+     */
+    return (*trk)->no_latlon && (*trk)->distance > 1e24 &&
+           !(*trk)->heartrate && !(*trk)->cadence;
+}
index 04ed235287d24c20ea509aa0653895080656267a..300bddf3fc674f6a8256a3fff55f3d0086d87d3a 100644 (file)
@@ -22,8 +22,11 @@ int32  GPS_A201_Send(const char *port, GPS_PWay *way, int32 n);
 
 int32  GPS_A300_Get(const char *port, GPS_PTrack **trk, pcb_fn cb);
 int32  GPS_A301_Get(const char *port, GPS_PTrack **trk, pcb_fn cb);
+int32  GPS_A302_Get(const char *port, GPS_PTrack **trk, pcb_fn cb);
 int32  GPS_A300_Send(const char *port, GPS_PTrack *trk, int32 n);
-int32  GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n); /*A302*/
+int32  GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n);
+int32  GPS_A302_Send(const char *port, GPS_PTrack *trk, int32 n,
+                    gpsdevh *fd);
 
 int32  GPS_D300_Get(GPS_PTrack *trk, int32 entries, gpsdevh *h);
 void   GPS_D300b_Get(GPS_PTrack *trk, UC *data);
@@ -32,9 +35,9 @@ void   GPS_D302b_Get(GPS_PTrack *trk, UC *data);
 void   GPS_D303b_Get(GPS_PTrack *trk, UC *data); /*D304*/
 void   GPS_D310_Get(GPS_PTrack *trk, UC *s);
 void   GPS_D311_Get(GPS_PTrack *trk, UC *s);
-void   GPS_D300_Send(UC *data, GPS_PTrack trk);
-void   GPS_D301_Send(UC *data, GPS_PTrack trk, int type);
-void   GPS_D304_Send(UC *data, GPS_PTrack trk);
+void   GPS_D300_Send(UC *data, GPS_PTrack trk, int32 *len);
+void   GPS_D301_Send(UC *data, GPS_PTrack trk, int32 *len, int type);
+void   GPS_D303_Send(UC *data, GPS_PTrack trk, int32 *len, int protoid);
 void   GPS_D310_Send(UC *data, GPS_PTrack trk, int32 *len);
 void   GPS_D311_Send(UC *data, GPS_PTrack trk, int32 *len);
 
@@ -59,28 +62,42 @@ int32  GPS_A800_Off(const char *port, gpsdevh **fd);
 int32  GPS_A800_Get(gpsdevh **fd, GPS_PPvt_Data *packet);
 void   GPS_D800_Get(GPS_PPacket packet, GPS_PPvt_Data *pvt);
 
-int32 GPS_A906_Get(const char *port, GPS_PLap **lap, pcb_fn cb);
-void GPS_D1011b_Get(GPS_PLap *Lap,UC *data); /*D906 D1001 D1015*/
+int32  GPS_A906_Get(const char *port, GPS_PLap **lap, pcb_fn cb);
+void   GPS_D1011b_Get(GPS_PLap *Lap,UC *data); /*D906 D1001 D1015*/
+
+int32  GPS_A1006_Get(const char *port, GPS_PCourse **crs, pcb_fn cb);
+int32  GPS_A1006_Send(const char *port, GPS_PCourse *crs, int32 n_crs,
+                      gpsdevh *fd);
+void   GPS_D1006_Get(GPS_PCourse *crs, UC *p);
+void   GPS_D1006_Send(UC *data, GPS_PCourse crs, int32 *len);
+
+int32  GPS_A1007_Get(const char *port, GPS_PCourse_Lap **clp, pcb_fn cb);
+int32  GPS_A1007_Send(const char *port, GPS_PCourse_Lap *clp, int32 n_clp,
+                      gpsdevh *fd);
+void   GPS_D1007_Get(GPS_PCourse_Lap *clp, UC *p);
+void   GPS_D1007_Send(UC *data, GPS_PCourse_Lap clp, int32 *len);
+
+int32  GPS_A1008_Get(const char *port, GPS_PCourse_Point **cpt, pcb_fn cb);
+int32  GPS_A1008_Send(const char *port, GPS_PCourse_Point *cpt, int32 n_cpt,
+                      gpsdevh *fd);
+void   GPS_D1012_Get(GPS_PCourse_Point *cpt, UC *p);
+void   GPS_D1012_Send(UC *data, GPS_PCourse_Point cpt, int32 *len);
 
 /* Unhandled documented protocols, as of:
   Garmin Device Interface Specification, May 19, 2006, Drawing Number: 001-00063-00 Rev. C
-A650 \96 FlightBook Transfer Protocol
-A1000 \96 Run Transfer Protocol
+A650  FlightBook Transfer Protocol
+A1000  Run Transfer Protocol
        Capability A1000: D1009
                D1000 D1010
-A1002 \96 Workout Transfer Protocol
+A1002  Workout Transfer Protocol
        Capability A1002: D1008
                D1002
        Capability A1003: D1003
-A1004 \96 Fitness User Profile Transfer Protocol
+A1004  Fitness User Profile Transfer Protocol
        Capability A1004: D1004
-A1005 \96 Workout Limits Transfer Protocol
+A1005  Workout Limits Transfer Protocol
        Capability A1005: D1005
-A1006 \96 Course Transfer Protocol
-       Capability A1006: D1006
-       Capability A1007: D1007
-       Capability A1008: D1012
-A1009 \96 Course Limits Transfer Protocol
+A1009  Course Limits Transfer Protocol
        Capability A1009: D1013
 */
 /* Unimplemted and Undocumented, as listed from the following device/sw:
index 883593ff3039eb7342f08e7653fc16bb7fee3d90..302c39d709e37760c1c5c9d8ef9474093305c340 100644 (file)
@@ -5,6 +5,8 @@
 ** @version 1.0 
 ** @modified Dec 28 1999 Alan Bleasby. First version
 ** @modified Copyright (C) 2005, 2006 Robert Lipe
+** @modified Copyright (C) 2007 Achim Schumacher
+** @modified Copyright (C) 2010 Martin Buck
 ** @@
 ** 
 ** This library is free software; you can redistribute it and/or
@@ -269,9 +271,14 @@ int32 GPS_Command_Send_Track(const char *port, GPS_PTrack *trk, int32 n)
        ret = GPS_A300_Send(port, trk, n);
        break;
     case pA301:
-    case pA302:
        ret = GPS_A301_Send(port, trk, n);
        break;
+    case pA302:
+       /* Units with A302 don't support track upload, so we convert the
+        * track to a course on the fly and send that instead
+        */
+       ret = GPS_Command_Send_Track_As_Course(port, trk, n);
+       break;
     default:
        GPS_Error("Send_Track: Unknown track protocol %d.", gps_trk_transfer);
        break;
@@ -672,20 +679,464 @@ int32 GPS_Command_Get_Lap(const char *port, GPS_PLap **lap, pcb_fn cb)
 
     return ret;
 }    
+
+/* @func GPS_Command_Get_Course ***************************************
+**
+** Get Courses from GPS. According to Garmin protocol specification, this
+** includes getting all course laps, course tracks and course points
+** from the device.
+**
+** @param [r] port [const char *] serial port
+** @param [w] crs [GPS_PCourse **] pointer to course array
+** @param [w] clp [GPS_PCourse_Lap **] pointer to course lap array
+** @param [w] trk [GPS_PTrack **] pointer to track array
+** @param [w] cpt [GPS_PCourse_Point **] pointer to course point array
+** @param [w] n_clp [int32 **] pointer to number of lap entries
+** @param [w] n_trk [int32 **] pointer to number of track entries
+** @param [w] n_cpt [int32 **] pointer to number of course point entries
+**
+** @return [int32] number of course entries
+************************************************************************/
+int32  GPS_Command_Get_Course
+                (const char *port,
+                 GPS_PCourse **crs,
+                 GPS_PCourse_Lap **clp,
+                 GPS_PTrack **trk,
+                 GPS_PCourse_Point **cpt,
+                 int32 *n_clp,
+                 int32 *n_trk,
+                 int32 *n_cpt,
+                 pcb_fn cb)
+{
+    int32 ret=0;
+
+    if(gps_course_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    switch(gps_course_transfer)
+    {
+       case pA1006:
+           ret = GPS_A1006_Get(port,crs,cb);
+           break;
+       default:
+           GPS_Error("Get_Course: Unknown course protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_lap_transfer)
+    {
+       case pA1007:
+           *n_clp = GPS_A1007_Get(port,clp, 0);
+           break;
+       default:
+           GPS_Error("Get_Course: Unknown course lap protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_trk_transfer)
+    {
+        case pA1012:
+           GPS_Error("Get_Course: Not implemented track protocol %d\n",
+                            gps_trk_transfer);
+           break;
+        case pA302:
+           *n_trk = GPS_A302_Get(port,trk,cb);
+           break;
+        default:
+           GPS_Error("Get_Course: Unknown course track protocol %d\n",
+                            gps_trk_transfer);
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_point_transfer)
+    {
+       case pA1008:
+           *n_cpt = GPS_A1008_Get(port,cpt, 0);
+           break;
+       default:
+           GPS_Error("Get_Course: Unknown course point protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    return ret;
+}
+
+
+/* @func GPS_Command_Send_Course ***************************************
+**
+** Send Courses to GPS. According to Garmin protocol specification, this
+** includes sending all course laps, course tracks and course points
+** to the device.
+**
+** Data integrity is being checked: only those course laps, tracks and
+** points are sent to the device which belong to a course in the course
+** array. Otherwise, those data will be silently dropped.
+**
+** @param [r] port [const char *] serial port
+** @param [w] crs [GPS_PCourse **] course array
+** @param [w] clp [GPS_PCourse_Lap *] course lap array
+** @param [w] trk [GPS_PTrack *] track array
+** @param [w] cpt [GPS_PCourse_Point *] course point array
+** @param [w] n_crs [int32] number of course entries
+** @param [w] n_clp [int32] number of lap entries
+** @param [w] n_trk [int32] number of track entries
+** @param [w] n_cpt [int32] number of course point entries
+**
+** @return [int32] Success
+************************************************************************/
+int32  GPS_Command_Send_Course
+                (const char *port,
+                 GPS_PCourse *crs,
+                 GPS_PCourse_Lap *clp,
+                 GPS_PTrack *trk,
+                 GPS_PCourse_Point *cpt,
+                 int32 n_crs,
+                 int32 n_clp,
+                 int32 n_trk,
+                 int32 n_cpt)
+{
+    gpsdevh *fd;
+    int32 ret_crs=0;
+    int32 ret_clp=0;
+    int32 ret_trk=0;
+    int32 ret_cpt=0;
+
+    if(gps_course_transfer == -1)
+       return GPS_UNSUPPORTED;
+
+    /* Initialize device communication:
+     * In contrast to other transfer protocols, this has to be handled here;
+     * shutting off communication in between the different parts
+     * could lead to data corruption on the device because all the courses
+     * and their associated lap and track data have to be sent in one
+     * transaction.
+     */
+    if(!GPS_Device_On(port,&fd))
+        return gps_errno;
+
+    switch(gps_course_transfer)
+    {
+       case pA1006:
+           ret_crs = GPS_A1006_Send(port,crs,n_crs,fd);
+           break;
+       default:
+           GPS_Error("Send_Course: Unknown course protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_lap_transfer)
+    {
+       case pA1007:
+           ret_clp = GPS_A1007_Send(port,clp,n_clp,fd);
+           break;
+       default:
+           GPS_Error("Send_Course: Unknown course lap protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_trk_transfer)
+    {
+        case pA1012:
+           GPS_Error("Send_Course: Not implemented track protocol %d\n",
+                            gps_trk_transfer);
+           break;
+        case pA302:
+           ret_trk = GPS_A302_Send(port,trk,n_trk,fd);
+           break;
+        default:
+           GPS_Error("Send_Course: Unknown course track protocol %d\n",
+                            gps_trk_transfer);
+           return PROTOCOL_ERROR;
+    }
+
+    switch(gps_course_point_transfer)
+    {
+       case pA1008:
+           ret_cpt = GPS_A1008_Send(port,cpt,n_cpt,fd);
+           break;
+       default:
+           GPS_Error("Send_Course: Unknown course point protocol");
+           return PROTOCOL_ERROR;
+    }
+
+    if(!GPS_Device_Off(fd))
+        return gps_errno;
+
+
+    return ret_crs * ret_clp * ret_trk * ret_cpt;
+}
+
+
+static uint32 Unique_Course_Index(GPS_PCourse *crs, int n_crs)
+{
+    uint32 idx;
+    int i;
+
+    for (idx=0; ; idx++)
+    {
+       for (i=0; i<n_crs; i++)
+           if (crs[i]->index==idx)
+               break; /* Already have this index */
+       if (i>=n_crs)
+           return idx; /* Found unused index */
+    }
+}
+
+
+static uint32 Unique_Track_Index(GPS_PCourse *crs, int n_crs)
+{
+    uint32 idx;
+    int i;
+
+    for (idx=0; ; idx++)
+    {
+       for (i=0; i<n_crs; i++)
+           if (crs[i]->track_index==idx)
+               break; /* Already have this index */
+       if (i>=n_crs)
+           return idx; /* Found unused index */
+    }
+}
+
+
+static void
+Course_Garbage_Collect(GPS_PCourse *crs, int *n_crs,
+                       GPS_PCourse_Lap *clp, int *n_clp,
+                       GPS_PTrack *ctk, int *n_ctk,
+                       GPS_PCourse_Point *cpt, int *n_cpt) {
+    int i, j;
+
+    /* Remove courses with duplicate names, keeping newest */
+restart_courses:
+    for (i=*n_crs-1; i>0; i--)
+    {
+       for (j=i-1; j>=0; j--)
+       {
+           if (!strcmp(crs[i]->course_name, crs[j]->course_name))
+           {
+               /* Remove course */
+               GPS_Course_Del(&crs[j]);
+               memmove(&crs[j], &crs[j+1], (*n_crs-j-1)*sizeof(*crs));
+               (*n_crs)--;
+               goto restart_courses;
+           }
+       }
+    }
+
+  /* Remove unreferenced laps */
+restart_laps:
+  for (i=0; i<*n_clp; i++)
+  {
+    for (j=0; j<*n_crs; j++)
+      if (crs[j]->index == clp[i]->course_index)
+        break;
+    if (j>=*n_crs)
+    {
+      /* Remove lap */
+      GPS_Course_Lap_Del(&clp[i]);
+      memmove(&clp[i], &clp[i+1], (*n_clp-i-1)*sizeof(*clp));
+      (*n_clp)--;
+      goto restart_laps;
+    }
+  }
+
+  /* Remove unreferenced tracks */
+restart_tracks:
+  for (i=0; i<*n_ctk; i++)
+  {
+    uint32 trk_idx;
+
+    if (!ctk[i]->ishdr)
+      continue;
+    trk_idx = strtoul(ctk[i]->trk_ident, NULL, 0);
+    for (j=0; j<*n_crs; j++)
+      if (crs[j]->index == trk_idx)
+        break;
+    if (j>=*n_crs)
+    {
+      /* Remove track */
+      for (j=i; j<*n_ctk; j++)
+      {
+        if (j!=i && ctk[j]->ishdr)
+          break;
+        GPS_Track_Del(&ctk[j]);
+      }
+      memmove(&ctk[i], &ctk[j], (*n_ctk-j)*sizeof(*ctk));
+      *(n_ctk) -= j-i;
+      goto restart_tracks;
+    }
+  }
+
+  /* Remove unreferenced course points */
+restart_course_points:
+  for (i=0; i<*n_cpt; i++)
+  {
+    for (j=0; j<*n_crs; j++)
+      if (crs[j]->index == cpt[i]->course_index)
+        break;
+    if (j>=*n_crs)
+    {
+      /* Remove course point */
+      GPS_Course_Point_Del(&cpt[i]);
+      memmove(&cpt[i], &cpt[i+1], (*n_cpt-i-1)*sizeof(*cpt));
+      (*n_cpt)--;
+      goto restart_course_points;
+    }
+  }
+}
+
+
+/* @func GPS_Command_Send_Track_As_Course ******************************
+**
+** Convert track log to course, then send to GPS. Since sending a course
+** to the device will erase all existing courses regardless of their
+** name or index, we first have to download all courses, merge the new
+** one and then send all courses at once.
+**
+** @param [r] port [const char *] serial port
+** @param [r] trk [GPS_PTrack *] track array
+** @param [r] n [int32] number of track entries
+**
+** @return [int32] success
+************************************************************************/
+
+int32 GPS_Command_Send_Track_As_Course(const char *port, GPS_PTrack *trk, int32 n)
+{
+    GPS_PCourse *crs = NULL;
+    GPS_PCourse_Lap *clp = NULL;
+    GPS_PTrack *ctk = NULL;
+    GPS_PCourse_Point *cpt = NULL;
+    int n_crs, n_clp=0, n_ctk=0, n_cpt=0;
+    int i, trk_end, trk_crs;
+    int32 ret;
+
+    /* Read existing courses from device */
+    n_crs = GPS_Command_Get_Course(port, &crs, &clp, &ctk, &cpt, &n_clp, &n_ctk, &n_cpt, NULL);
+    if (n_crs < 0) return n_crs;
+
+    /* Create new course+lap+track points for each track */
+    trk_crs = n_crs;
+    for (i=0;i<n;i++) {
+       if (!trk[i]->ishdr)
+           continue;
+
+       /* Find end of track */
+       for (trk_end=i; trk_end<n-1; trk_end++)
+           if (trk[trk_end+1]->ishdr)
+               break;
+       if (trk_end==i)
+           trk_end=0; /* Empty track */
+
+       /* Create & append course */
+       crs = realloc(crs, (n_crs+1) * sizeof(GPS_PCourse));
+       if (!crs) return MEMORY_ERROR;
+       crs[n_crs] = GPS_Course_New();
+       if (!crs[n_crs]) return MEMORY_ERROR;
+
+       crs[n_crs]->index = Unique_Course_Index(crs, n_crs);
+       strncpy(crs[n_crs]->course_name, trk[i]->trk_ident,
+               sizeof(crs[n_crs]->course_name)-1);
+
+       crs[n_crs]->track_index = Unique_Track_Index(crs, n_crs);
+
+       /* Create & append new lap */
+       clp = realloc(clp, (n_clp+1) * sizeof(GPS_PCourse_Lap));
+       if (!clp) return MEMORY_ERROR;
+       clp[n_clp] = GPS_Course_Lap_New();
+       if (!clp[n_clp]) return MEMORY_ERROR;
+
+       clp[n_clp]->course_index = crs[n_crs]->index; /* Index of associated course */
+       clp[n_clp]->lap_index = 0; /* Lap index, unique per course */
+       clp[n_clp]->total_time = 0; /* FIXME: Calculate */
+       clp[n_clp]->total_dist = 0; /* FIXME: Calculate */
+       if (trk_end)
+       {
+           clp[n_clp]->begin_lat = trk[i+1]->lat;
+           clp[n_clp]->begin_lon = trk[i+1]->lon;
+           clp[n_clp]->end_lat = trk[trk_end]->lat;
+           clp[n_clp]->end_lon = trk[trk_end]->lon;
+       }
+       else
+       {
+           clp[n_clp]->begin_lat = 0x7fffffff;
+           clp[n_clp]->begin_lon = 0x7fffffff;
+           clp[n_clp]->end_lat = 0x7fffffff;
+           clp[n_clp]->end_lon = 0x7fffffff;
+       }
+       clp[n_clp]->avg_heart_rate = 0; /* FIXME: Calculate */
+       clp[n_clp]->max_heart_rate = 0; /* FIXME: Calculate */
+       clp[n_clp]->intensity = 0;
+       clp[n_clp]->avg_cadence = 0xff; /* FIXME: Calculate */
+       n_crs++;
+       n_clp++;
+    }
+
+    /* Append new track points */
+    ctk = realloc(ctk, (n_ctk+n) * sizeof(GPS_PTrack));
+    if (!ctk) return MEMORY_ERROR;
+
+    for (i=0;i<n;i++) {
+       ctk[n_ctk] = GPS_Track_New();
+       if (!ctk[n_ctk]) return MEMORY_ERROR;
+       *ctk[n_ctk] = *trk[i];
+
+       if (ctk[n_ctk]->ishdr)
+       {
+           /* Index of new track, must match the track index in associated course */
+           memset(ctk[n_ctk]->trk_ident, 0, sizeof(ctk[n_ctk]->trk_ident));
+           sprintf(ctk[n_ctk]->trk_ident, "%d", crs[trk_crs]->track_index);
+           trk_crs++;
+       }
+       n_ctk++;
+    }
+
+    /* Remove course data that's no longer needed */
+    Course_Garbage_Collect(crs, &n_crs, clp, &n_clp, ctk, &n_ctk, cpt, &n_cpt);
+
+    /* Finally send courses including new ones to device */
+    ret = GPS_Command_Send_Course(port, crs, clp, ctk, cpt,
+                                  n_crs, n_clp, n_ctk, n_cpt);
+
+    for (i=0;i<n_crs;i++)
+    {
+       GPS_Course_Del(&crs[i]);
+    }
+    free(crs);
+
+    for (i=0;i<n_clp;i++)
+    {
+       GPS_Course_Lap_Del(&clp[i]);
+    }
+    free(clp);
+
+    for (i=0;i<n_ctk;i++)
+    {
+       GPS_Track_Del(&ctk[i]);
+    }
+    free(ctk);
+
+    for (i=0;i<n_cpt;i++)
+    {
+       GPS_Course_Point_Del(&cpt[i]);
+    }
+    free(cpt);
+
+    return ret;
+}
+
  /*Stubs for unimplemented stuff*/
 int32  GPS_Command_Get_Workout(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **)){
   return 0;
 }  
+
 int32  GPS_Command_Get_Fitness_User_Profile(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **)){
   return 0;
 }  
+
 int32  GPS_Command_Get_Workout_Limits(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **)){
   return 0;
 }  
-int32  GPS_Command_Get_Course(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **)){
-  return 0;
-}  
+
 int32  GPS_Command_Get_Course_Limits(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **)){
   return 0;
 }  
-
index 4c490cd42a617396ccd9a86ecc97be75c3031250..d0d5b7f49fe466efedbc9f3af9131f27596c7cb8 100644 (file)
@@ -39,10 +39,14 @@ int32  GPS_Command_Send_Route(const char *port, GPS_PWay *way, int32 n);
 
 int32  GPS_Command_Get_Lap(const char *port, GPS_PLap **lap, int (*cb)(int, struct GPS_SWay **));
 
+int32  GPS_Command_Send_Course(const char *port, GPS_PCourse *crs, GPS_PCourse_Lap *clp,
+                               GPS_PTrack *trk, GPS_PCourse_Point *cpt,
+                               int32 n_crs, int32 n_clp, int32 n_trk, int32 n_cpt);
+int32  GPS_Command_Send_Track_As_Course(const char *port, GPS_PTrack *trk, int32 n);
+
 int32  GPS_Command_Get_Workout(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **));
 int32  GPS_Command_Get_Fitness_User_Profile(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **));
 int32  GPS_Command_Get_Workout_Limits(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **));
-int32  GPS_Command_Get_Course(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **));
 int32  GPS_Command_Get_Course_Limits(const char *port, void **lap, int (*cb)(int, struct GPS_SWay **));
 #endif
 
index bfc92bcb79789d0fbac3da8a037cad2ba2ec1a67..8425422eccc111cf3d394274edc7587617432972 100644 (file)
@@ -6,6 +6,7 @@
 ** @modified December 28th 1999 Alan Bleasby. First version
 ** @modified June 29th 2000 Alan Bleasby. NMEA additions
 ** @modified Copyright (C) 2006 Robert Lipe
+** @modified Copyright (C) 2007 Achim Schumacher
 ** @@
 ** 
 ** This library is free software; you can redistribute it and/or
@@ -333,3 +334,122 @@ void GPS_Lap_Del(GPS_PLap *thys)
 
     return;
 }
+
+
+/* @func GPS_Course_New ***********************************************
+**
+** Course constructor
+**
+** @return [GPS_PCourse] virgin Course
+**********************************************************************/
+GPS_PCourse GPS_Course_New(void)
+{
+    GPS_PCourse ret;
+
+    if(!(ret=(GPS_PCourse)calloc(1,sizeof(GPS_OCourse))))
+    {
+       perror("malloc");
+       fprintf(stderr,"GPS_Course_New: Insufficient memory");
+       fflush(stderr);
+       return NULL;
+    }
+
+    return ret;
+}
+
+
+
+/* @func GPS_Course_Del ***********************************************
+**
+** Course destructor
+**
+** @param [w] thys [GPS_PCourse *] Course to delete
+**
+** @return [void]
+**********************************************************************/
+void GPS_Course_Del(GPS_PCourse *thys)
+{
+    free((void *)*thys);
+
+    return;
+}
+
+/* @func GPS_Course_Lap_New ***********************************************
+**
+** Course_Lap constructor
+**
+** @return [GPS_PCourse_Lap] virgin course lap
+**********************************************************************/
+
+GPS_PCourse_Lap GPS_Course_Lap_New(void)
+{
+    GPS_PCourse_Lap ret;
+
+    if(!(ret=(GPS_PCourse_Lap)calloc(1,sizeof(GPS_OCourse_Lap))))
+    {
+       perror("malloc");
+       fprintf(stderr,"GPS_Course_Lap_New: Insufficient memory");
+       fflush(stderr);
+       return NULL;
+    }
+
+    return ret;
+}
+
+
+
+/* @func GPS_Course_Lap_Del ***********************************************
+**
+** Course_Lap destructor
+**
+** @param [w] thys [GPS_PCourse_Lap *] course lap to delete
+**
+** @return [void]
+**********************************************************************/
+
+void GPS_Course_Lap_Del(GPS_PCourse_Lap *thys)
+{
+    free((void *)*thys);
+
+    return;
+}
+
+/* @func GPS_Course_Point_New ***********************************************
+**
+** Course_Point constructor
+**
+** @return [GPS_PCourse_Point] virgin course point
+**********************************************************************/
+
+GPS_PCourse_Point GPS_Course_Point_New(void)
+{
+    GPS_PCourse_Point ret;
+
+    if(!(ret=(GPS_PCourse_Point)calloc(1,sizeof(GPS_OCourse_Point))))
+    {
+       perror("malloc");
+       fprintf(stderr,"GPS_Course_Point_New: Insufficient memory");
+       fflush(stderr);
+       return NULL;
+    }
+
+    return ret;
+}
+
+
+
+/* @func GPS_Course_Point_Del ***********************************************
+**
+** Course_Point destructor
+**
+** @param [w] thys [GPS_PCourse_Point *] course lap to delete
+**
+** @return [void]
+**********************************************************************/
+
+void GPS_Course_Point_Del(GPS_PCourse_Point *thys)
+{
+    free((void *)*thys);
+
+    return;
+}
index 2f5bbc2cef9159d5247efc10f4b5d7d599822ddf..91292f7d6122c3a634111ce18b5651ceb193a035 100644 (file)
@@ -9,18 +9,24 @@ extern "C"
 
 #include "gps.h"
 
-GPS_PPacket   GPS_Packet_New(void);
-void          GPS_Packet_Del(GPS_PPacket *thys);    
-GPS_PPvt_Data GPS_Pvt_New(void);
-void          GPS_Pvt_Del(GPS_PPvt_Data *thys);
-GPS_PAlmanac  GPS_Almanac_New(void);
-void          GPS_Almanac_Del(GPS_PAlmanac *thys);
-GPS_PTrack    GPS_Track_New(void);
-void          GPS_Track_Del(GPS_PTrack *thys);
-GPS_PWay      GPS_Way_New(void);
-void          GPS_Way_Del(GPS_PWay *thys);
-GPS_PLap         GPS_Lap_New(void);
-void          GPS_Lap_Del(GPS_PLap *thys);
+GPS_PPacket       GPS_Packet_New(void);
+void              GPS_Packet_Del(GPS_PPacket *thys);
+GPS_PPvt_Data     GPS_Pvt_New(void);
+void              GPS_Pvt_Del(GPS_PPvt_Data *thys);
+GPS_PAlmanac      GPS_Almanac_New(void);
+void              GPS_Almanac_Del(GPS_PAlmanac *thys);
+GPS_PTrack        GPS_Track_New(void);
+void              GPS_Track_Del(GPS_PTrack *thys);
+GPS_PWay          GPS_Way_New(void);
+void              GPS_Way_Del(GPS_PWay *thys);
+GPS_PLap          GPS_Lap_New(void);
+void              GPS_Lap_Del(GPS_PLap *thys);
+GPS_PCourse       GPS_Course_New(void);
+void              GPS_Course_Del(GPS_PCourse *thys);
+GPS_PCourse_Lap   GPS_Course_Lap_New(void);
+void              GPS_Course_Lap_Del(GPS_PCourse_Lap *thys);
+GPS_PCourse_Point GPS_Course_Point_New(void);
+void              GPS_Course_Point_Del(GPS_PCourse_Point *thys);
 
 #endif